<?php
declare (strict_types=1);

namespace sws;

use app\event\SwsEvent;
use Swoole\Process;
use Throwable;

class Server
{
    const HTTP = 'Http';

    const WEB_SOCKET = 'WebSocket';


    /**
     * 服务名
     * @var string
     */
    protected $name;

    /**
     * 服务配置
     * @var array
     */
    protected $config;

    /**
     * master进程文件路径
     * @var string
     */
    protected $pidFile;

    /**
     * swoole服务
     * @var \Swoole\Server
     */
    protected $swooleServer;

    protected $events = [
        'onStart',
        'onShutdown',
        'onWorkerStart',
        'onWorkerStop',
        'onWorkerExit',
        'onConnect',
        'onReceive',
        'onPacket',
        'onClose',
        'onTask',
        'onFinish',
        'onPipeMessage',
        'onWorkerError',
        'onManagerStart',
        'onManagerStop',
        'onBeforeReload',
        'onBeforeReload',
        'onAfterReload',
    ];


    public function __construct(string $serverName, array $config)
    {
        $this->name = $serverName;
        $this->config = $config;
        if (!empty($config['setting']['pid_file'])) {
            $this->pidFile = $config['setting']['pid_file'];
        } else {
            //以服务名设置pid路径
            $path = BASE_PATH . $serverName . '.pid';
            $this->config['setting']['pid_file'] = $path;
            $this->pidFile = $path;
        }
    }

    /**
     * 获取实例
     * @return Server
     */
    public static function getInstance(): Server
    {
        return Container::getInstance()->get(Server::class);
    }

    /**
     * 重启服务
     */
    public function restart()
    {
        if ($this->isRunning()) {
            $this->stop();
        }
        $this->start();
    }

    /**
     * 判断master进程是否运行
     * @return bool
     */
    public function isRunning(): bool
    {
        $pid = $this->getPid();
        return $pid && Process::kill($pid, 0);
    }

    /**
     * 获取master进程id
     * @return int
     */
    public function getPid(): int
    {
        if (is_readable($this->pidFile)) {
            return (int)file_get_contents($this->pidFile);
        }
        return 0;
    }

    /**
     * 停止服务
     */
    public function stop()
    {
        if (!$this->isRunning()) {
            Console::getInstance()->writeln($this->name . " server is not running.", Console::ERROR);
            return;
        }

        if (!isset($this->config['type']) || !in_array($this->config['type'], [self::HTTP, self::WEB_SOCKET])) {
            Console::getInstance()->writeln('Unsupported server type.', Console::ERROR);
            return;
        }

        Console::getInstance()->writeln("stopping {$this->name} server...");

        if ($this->killProcess(SIGTERM, 10)) {
            Console::getInstance()->writeln("Unable to stop server.", Console::ERROR);
            return;
        }

        is_file($this->pidFile) && @unlink($this->pidFile);
        Console::getInstance()->writeln("stopping success.", Console::SUCCESS);
    }

    /**
     * 杀死进程
     * @param     $signal
     * @param int $wait
     * @return bool
     */
    protected function killProcess($signal, int $wait = 0): bool
    {
        $pid = $this->getPid();
        $pid > 0 && Process::kill($pid, $signal);

        if ($wait > 0) {
            $start = time();
            do {
                usleep(100000);
                if (!$this->isRunning()) {
                    break;
                }
            } while (time() < $start + $wait);
        }
        return $this->isRunning();
    }

    /**
     * 启动服务
     */
    public function start()
    {
        if ($this->isRunning()) {
            Console::getInstance()->writeln($this->name . " service is running.", Console::ERROR);
            return;
        }

        if (!isset($this->config['type']) || !in_array($this->config['type'], [self::HTTP, self::WEB_SOCKET])) {
            Console::getInstance()->writeln('Unsupported server type.', Console::ERROR);
            return;
        }
        Console::getInstance()->writeSWS();

        Event::getInstance()->trigger([SwsEvent::class, 'serverCreate']);
        switch ($this->config['type']) {
            case self::HTTP:
                $this->swooleServer = new \Swoole\Http\Server($this->config['host'], $this->config['port'], $this->config['mode'], $this->config['sock_type']);
                break;
            case self::WEB_SOCKET:
                $this->swooleServer = new \Swoole\WebSocket\Server($this->config['host'], $this->config['port'], $this->config['mode'], $this->config['sock_type']);
                break;

        }
        //合并公共设置
        $setting = array_merge(Config::getInstance()->get('server.setting', []), $this->config['setting']);
        $this->swooleServer->set($setting);
        $this->bindEvent();
        Console::getInstance()->writeln($this->name . ' server started success: <' . $this->config['type'] . '://' . $this->config['host'] . ':' . $this->config['port'] . '>', Console::SUCCESS);
        $this->swooleServer->start();
    }

    /**
     * 绑定swoole事件
     */
    public function bindEvent()
    {
        foreach ($this->events as $event) {
            $this->swooleServer->on(substr($event, 2), function (...$vars) use ($event) {
                try {
                    Event::getInstance()->trigger($event, func_get_args());
                } catch (Throwable $e) {
                    Error::getInstance()->appException($e);
                }
            });
        }

        //http服务额外绑定onRequest事件
        if ($this->config['type'] == self::HTTP) {
            $this->swooleServer->on('Request', function ($request, $response) {
                try {
                    Container::getInstance()->bind('request', $request);
                    Container::getInstance()->bind('response', $response);
                    //触发事件
                    Event::getInstance()->trigger('onRequest', func_get_args());
                } catch (Throwable $e) {
                    Error::getInstance()->appException($e);
                }
                //清理协程容器
                Container::getInstance()->clear();
            });
        }
    }

    /**
     * 热重启服务
     */
    public function reload()
    {
        if (!$this->isRunning()) {
            Console::getInstance()->writeln($this->name . " server is not running.", Console::ERROR);
            return;
        }
        Console::getInstance()->writeln("reloading {$this->name} server...");

        if (!$this->killProcess(SIGUSR1)) {
            Console::getInstance()->writeln("Unable to reload server.", Console::ERROR);
            return;
        }
        Console::getInstance()->writeln("reloading success.", Console::SUCCESS);
    }

    /**
     * 获取服务名
     * @return string
     */
    public function getName(): string
    {
        return $this->name;
    }

    /**
     * 获取swoole服务
     * @return \Swoole\Server
     */
    public function getSwooleServer(): \Swoole\Server
    {
        return $this->swooleServer;
    }


}